/*
Copyright (c) 2009-2010, Warwick Warp Limited
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Warwick Warp Limited nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL WARWICK WARP LIMITED BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef WOPTIONS_ARGUMENT_BINDERS_H_INCLUDED
#define WOPTIONS_ARGUMENT_BINDERS_H_INCLUDED

#include <string>

#include "detail/boost_headers.h"
#include "detail/convertorthrow.h"
#include "argumentexception.h"

namespace woptions {

namespace basic {
/*!
	\brief Base class for classes that respond to command line argument
	
	Derived classes must be copy-constructable.
	
	\ingroup gargbind
*/
template<typename char_t>
struct bind_argument
{
	//! \brief Basic string type
	typedef std::basic_string<char_t> string_type;
	virtual ~bind_argument() {}
	
	/*!
		\brief Handle a command line argument. 
		\throws ArgumentException if an error occurs
	*/
	virtual void operator()(const std::basic_string<char_t>& arg) = 0;
};

} // namespace basic

namespace detail {
	
//! Create a binder that simply discards an argument
template<typename char_t>
class bind_argument_ignore : public basic::bind_argument<char_t>
{
public:
	void operator()(const std::basic_string<char_t>& arg)
	{
	}
};

//! Bind an argument to a variable
template<typename char_t, typename TYPE>
class bind_argument_var_type : public basic::bind_argument<char_t>
{
public:
	//! Bind an argument to a variable
	bind_argument_var_type(
		TYPE& var //!< Variable to set when the argument is parsed
		) : m_var(&var) {}
	//! Convert argument to the variable type and set variable
	void operator()(const std::basic_string<char_t>& arg)
	{
		*m_var = detail::convert_or_throw<TYPE>(
			arg,
			"Invalid argument");
	}
	
private:
	TYPE *m_var;
};

//! Bind an argument to a unary function
template<typename char_t, typename FUN>
class bind_argument_fun_type : public basic::bind_argument<char_t>
{
public:
	//! Function argument list
	typedef typename boost::function_types::parameter_types<FUN> arg_types;
	//! Function argument type
	typedef typename boost::mpl::at<arg_types, boost::mpl::int_<0> >::type arg_type;
	//! Bind an argument to a unary function
	bind_argument_fun_type(
		FUN f //!< Function to call when the argument is parsed
		) : m_func(f) {}
	//! Convert argument to function argument type and call function
	void operator()(const std::basic_string<char_t>& arg)
	{
		m_func(
			detail::convert_or_throw<typename detail::remove_quals<arg_type>::type>(arg,"Invalid argument")
			);
	}
	
private:
	FUN m_func;
}; 

//! Bind an argument to a member function
template<typename char_t, typename CLS, typename FUN>
class bind_argument_mem_fun_type : public basic::bind_argument<char_t>
{
public:
	//! Function argument list
	typedef typename boost::function_types::parameter_types<FUN> arg_types;
	//! Function argument type
	typedef typename boost::mpl::at<arg_types, boost::mpl::int_<1> >::type arg_type;
	//! Bind an argument to a member function
	bind_argument_mem_fun_type(
		CLS *cls, //!< Object to call the function on
		FUN f     //!< Unary member function to call
		) : m_cls(cls), m_func(f) {}
	//! Convert argument to function argument type and call function
	void operator()(const std::basic_string<char_t>& arg)
	{
		(m_cls->*m_func)(
			detail::convert_or_throw<typename detail::remove_quals<arg_type>::type>(arg,"Invalid argument")
			);
	}
	
private:
	CLS *m_cls;
	FUN m_func;
}; 

} // namespace detail

namespace basic {

/*! 
	\brief Ignore a particular argument (wide character version)
	
	Particularly useful for arg0.
	
	\ingroup gargbind
*/
template<typename char_t>
inline detail::bind_argument_ignore<char_t> bind_argument_ignore()
{
	return detail::bind_argument_ignore<char_t>();
}

/*!
	\brief Bind an argument to a variable (wide character version)
	
	Uses std::stringstream to convert to the variable type.
	
	\ingroup gargbind
*/
template<typename char_t, class TYPE>
inline detail::bind_argument_var_type<char_t,TYPE> bind_argument_var(TYPE& var)
{
	return detail::bind_argument_var_type<char_t,TYPE>(var);
}

/*!
	\brief Bind an argument to a function pointer or functor (wide character version)
	
	The function must take a single argument, the argument may be of any type
	and will be automatically converted to using a stringstream.
	
	\ingroup gargbind
*/
template<typename char_t, class FUN>
inline detail::bind_argument_fun_type<char_t,FUN> bind_argument_fun(FUN f)
{
	return detail::bind_argument_fun_type<char_t,FUN>(f);
}

/*!
	\brief Bind an argument to a member function pointer or functor (wide character version)
	
	The function must take a single argument, the argument may be of any type
	and will be automatically converted to using a stringstream.
	
	\ingroup gargbind
*/
template<typename char_t, class CLS, class FUN>
inline detail::bind_argument_mem_fun_type<char_t,CLS,FUN> bind_argument_mem_fun(CLS *o, FUN f)
{
	return detail::bind_argument_mem_fun_type<char_t,CLS,FUN>(o,f);
}
	
} // namespace basic

namespace wide {
	
/*! 
	\brief Base class for classes that respond to command line argument (wide character version)
	\ingroup wargbind
*/
typedef basic::bind_argument<wchar_t> bind_argument;
	
/*! 
	\brief Ignore a particular argument (wide character version)
	
	Particularly useful for arg0.
	
	\ingroup wargbind
*/
inline detail::bind_argument_ignore<wchar_t> bind_argument_ignore()
{
	return detail::bind_argument_ignore<wchar_t>();
}

/*!
	\brief Bind an argument to a variable (wide character version)
	
	Uses std::stringstream to convert to the variable type.

	\ingroup wargbind
*/
template<class TYPE>
inline detail::bind_argument_var_type<wchar_t,TYPE> bind_argument_var(TYPE& var)
{
	return detail::bind_argument_var_type<wchar_t,TYPE>(var);
}

/*!
	\brief Bind an argument to a function pointer or functor (wide character version)
	
	The function must take a single argument, the argument may be of any type
	and will be automatically converted to using a stringstream.

	\ingroup wargbind
*/
template<class FUN>
inline detail::bind_argument_fun_type<wchar_t,FUN> bind_argument_fun(FUN f)
{
	return detail::bind_argument_fun_type<wchar_t,FUN>(f);
}

/*!
	\brief Bind an argument to a member function pointer or functor (wide character version)
	
	The function must take a single argument, the argument may be of any type
	and will be automatically converted to using a stringstream.

	\ingroup wargbind
*/
template<class CLS, class FUN>
inline detail::bind_argument_mem_fun_type<wchar_t,CLS,FUN> bind_argument_mem_fun(CLS *o, FUN f)
{
	return detail::bind_argument_mem_fun_type<wchar_t,CLS,FUN>(o,f);
}
	
} // namespace wide

/*! 
	\brief Base class for classes that respond to command line argument (char)
	\ingroup cargbind
*/
typedef basic::bind_argument<char> bind_argument;

/*! 
	\brief Ignore a particular argument 
	
	Particularly useful for arg0.

	\ingroup cargbind
*/
inline detail::bind_argument_ignore<char> bind_argument_ignore()
{
	return detail::bind_argument_ignore<char>();
}

/*!
	\brief Bind an argument to a variable
	
	Uses std::stringstream to convert to the variable type.

	\ingroup cargbind
*/
template<class TYPE>
inline detail::bind_argument_var_type<char,TYPE> bind_argument_var(TYPE& var)
{
	return detail::bind_argument_var_type<char,TYPE>(var);
}

/*!
	\brief Bind an argument to a function pointer or functor
	
	The function must take a single argument, the argument may be of any type
	and will be automatically converted to using a stringstream.

	\ingroup cargbind
*/
template<class FUN>
inline detail::bind_argument_fun_type<char,FUN> bind_argument_fun(FUN f)
{
	return detail::bind_argument_fun_type<char,FUN>(f);
}

/*!
	\brief Bind an argument to a member function pointer or functor
	
	The function must take a single argument, the argument may be of any type
	and will be automatically converted to using a stringstream.

	\ingroup cargbind
*/
template<class CLS, class FUN>
inline detail::bind_argument_mem_fun_type<char,CLS,FUN> bind_argument_mem_fun(CLS *o, FUN f)
{
	return detail::bind_argument_mem_fun_type<char,CLS,FUN>(o,f);
}

} // namespace woptions

#endif
